package spea;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;

import common.GeneticOperations;


public class IndividualSet<Genotype> {
	private int size;
	private List<Individual<Genotype>> idividuals;
	private GeneticOperations<Genotype> go;
	private Random random = new Random(1L);


	public IndividualSet(int size, GeneticOperations<Genotype> go) {
		this.size = size;
		idividuals = new ArrayList<Individual<Genotype>>(size);
		this.go = go;
	}

	public IndividualSet(int size, GeneticOperations<Genotype> go, ArrayList<Individual<Genotype>> individuals) {
		this.size = size;
		this.idividuals = individuals;
		this.go = go;
	}


	public void generateRandomIndividuals() {
		int i;
		Individual<Genotype> creature;

		for (i = 0; i < this.size; i++) {
			creature = new Individual<Genotype>(go.createRandomGenotype(), go);
			idividuals.add(creature);
		}
	}


	public void calculateFitness(IndividualSet<Genotype> archive) {

		//zero parameters
		for (Individual<Genotype> creature:  this.getIndividuals()) {
			creature.zero();
		}
		for (Individual<Genotype> creature: archive.getIndividuals()) {
			creature.zero();
		}

		// calculate strength for population and archive
		for (Individual<Genotype> creature:  this.getIndividuals()) {
			creature.calculateStrength(this.getIndividuals(), archive.getIndividuals());
		}
		for (Individual<Genotype> creature: archive.getIndividuals()) {
			creature.calculateStrength(this.getIndividuals(), archive.getIndividuals());
		}

		// calculate row fitness
		for (Individual<Genotype> creature:  this.getIndividuals()) {
			creature.calculateRowFitness();
		}
		for (Individual<Genotype> creature: archive.getIndividuals()) {
			creature.calculateRowFitness();
		}

		// calculate density
		int k = (int) Math.round(Math.sqrt(this.getSize() + archive.getSize()));

		for (Individual<Genotype> creature:  this.getIndividuals()) {
			creature.calculateDensity(this.getIndividuals(), archive.getIndividuals(), k);
		}
		for (Individual<Genotype> creature: archive.getIndividuals()) {
			creature.calculateDensity(this.getIndividuals(), archive.getIndividuals(), k);
		}
	}


	public IndividualSet<Genotype> copyNdIndividuals(IndividualSet<Genotype> archive) {

		ArrayList<Individual<Genotype>> newArchiveInd = new ArrayList<Individual<Genotype>>();
		IndividualSet<Genotype> newArchive;

		for (Individual<Genotype> creatureTmp: this.getIndividuals()) {
			if (creatureTmp.getRowFitness() == 0.0) {
				newArchiveInd.add(creatureTmp);
			}
		}
		for (Individual<Genotype> creatureTmp: archive.getIndividuals()) {
			if (creatureTmp.getRowFitness() == 0.0) {
				newArchiveInd.add(creatureTmp);
			}
		}

		// if size of newArchive is less then AN, it must be complement
		int newArchiveSize = newArchiveInd.size();
		if (newArchiveSize < archive.getSize()) {
			FitnessComparator<Genotype> fc = new FitnessComparator<Genotype>();
			List<Individual<Genotype>> randPopulation = this.getIndividuals();
			randPopulation.addAll(archive.getIndividuals());
			Collections.sort(randPopulation, fc);
			int dif = archive.getSize() - newArchiveInd.size();

			for (int i = 0; i < dif; i++) {
				Individual<Genotype> creatureTmp = randPopulation.remove(0);
				newArchiveInd.add(creatureTmp);
			}
		}
		// if size of newArchive is grater then AN, it must be reduced
		else if (newArchiveInd.size() > archive.getSize()) {
			int red;
			FitnessComparator<Genotype> fc = new FitnessComparator<Genotype>();
			
			Collections.sort(newArchiveInd, fc);
			red = newArchiveInd.size() / 2;
			while (newArchiveInd.size() > archive.getSize()) {
				for (int i = red; i < (newArchiveInd.size())
				& (newArchiveInd.size() > archive.getSize()); i += red) {
					newArchiveInd.remove(i);
				}
				red /= 2;
			}
		}

		newArchive = new IndividualSet<Genotype>(newArchiveInd.size(), this.go, newArchiveInd);
		return newArchive;
	}


	public IndividualSet<Genotype> matingSelection() {
		ArrayList<Individual<Genotype>> youngCreatures = new ArrayList<Individual<Genotype>>();
		int index1, index2;
		Individual<Genotype> creature1;
		Individual<Genotype> creature2;
		IndividualSet<Genotype> retIndividuals;

		for (int i = 0; youngCreatures.size() < this.getSize(); i++) {
			index1 = random.nextInt(this.getSize());
			creature1 = this.getIndividuals().get(index1);

			index2 = random.nextInt(this.getSize() - 1);
			if(index2 == index1)
				if(index2 < (this.getSize() - 1))
					index2++;
				else
					index2--;
			creature2 = this.getIndividuals().get(index2);

			if (creature1.getFitness() <= creature2.getFitness()) {
				youngCreatures.add(creature1);
				//creatures.remove(creature1);
			} else {
				youngCreatures.add(creature2);
				//creatures.remove(creature2);
			}
		}

		retIndividuals = new IndividualSet<Genotype>(youngCreatures.size(), this.go, youngCreatures);
		return retIndividuals;
	}


	public void recombination() {
		List<Individual<Genotype>> recombinated = new ArrayList<Individual<Genotype>>();
		long index1, index2;
		Individual<Genotype> creature1, creature2;
		List<Individual<Genotype>> newCreatures = null;

		//System.out.println("Rozmiar puli rodzicielskiej w srodku " + this.getSize() + " " + recombinated.size());
		
		while ((recombinated.size() < this.getSize()) & (this.getSize() > 0)) {
			//System.out.println("Rozmiar puli rodzicielskiej w srodku " + this.getSize());
			index1 = random.nextInt(this.getSize());
			creature1 = this.getIndividuals().get((int) index1);
			index2 = random.nextInt(this.getSize());
			creature2 = this.getIndividuals().get((int) index2);
			//System.out.println("index1 " + index1 + " " + index2);
			newCreatures = creature1.crossover(creature2);

			this.getIndividuals().remove(creature1);
			this.getIndividuals().remove(creature2);
			
			this.setSize(this.getSize()-2);
			/*
			 * Uwazam, ze crossover powinien zwracac 2 osobniki!!!!
			 * Ale na razie zadawalam sie tym, co jest
			 * */
			recombinated.add(newCreatures.get(0));
			recombinated.add(newCreatures.get(0));

		}
		if (recombinated.size() > this.getSize()) {
			recombinated.remove(recombinated.size() - 1);
		}

		this.idividuals = recombinated; 
		//return recombinated;
	}


	public void mutation() {

		for (int j = 0; j < this.getSize(); j++) {
			Individual<Genotype> creature = this.getIndividuals().get(j);
			creature.mutate();
		}
	}


	public int getSize() {
		return this.size;
	}

	public void setSize(int size) {
		this.size = size;
	}

	public List<Individual<Genotype>> getIndividuals() {
		return this.idividuals;
	}
}
